<?php
class RL_Principal_Auth_Openid extends RL_Principal_Auth_Common
{
	public $error = NULL;
	private $login_url;
	private $trust_url;
	private $openid_server_url;
	
	public function __construct ($args)
	{
		$this->login_url = isset($args['login_url']) ? $args['login_url'] : NULL;
		$this->trust_url = $args['trust_url'];
		$this->openid_server_url = $args["openid_srv_url"];
	} // end of __construct
	
	private function getOpenid_url($url) {
		require_once 'includes/url.php';

		$openid_url    = Url::createFromString($url);
		// HACK : 2008-01-28 : lucky  #317 for LiveJournal.com 
		// использовать канонический openid для пользователей ЖЖ
		// user_name.livejournal.com => user-name.livejournal.com
		if ($openid_url->getHost() && preg_match('#^(.+)(\.livejournal\.com)$#', $openid_url->getHost(), $ms)) {
			// match with "http://user_name.livejournal.com"
			$openid_url->setHost(strtr($ms[1], array('_'=>'-')).$ms[2]);
		} elseif (!$openid_url->getHost() && preg_match('#^(.+)(\.livejournal\.com)$#', $openid_url->getPath(), $ms)) {
			// match with "user_name.livejournal.com"
			// (URI scheme omited)
			$openid_url->setHost(strtr($ms[1], array('_'=>'-')).$ms[2]);
			$openid_url->setPath('');
		}
		return $openid_url->getUrl();
	}
	
	private function getLoginUrl($input) {
		require_once 'includes/url.php';
		if (isset($this->login_url)) {
				$login_url = Url::createFromString($this->login_url);
		} else {
			// считаем адрес текущего запроса.
			$my_url =
				strtolower(substr($_SERVER['SERVER_PROTOCOL'], 0,
					strpos($_SERVER['SERVER_PROTOCOL'], '/')))
					.'://'.$_SERVER['HTTP_HOST'].urldecode($_SERVER['REQUEST_URI']);
			
			$login_url = Url::createFromString($my_url);
			if ($input instanceof RL_Principal_Input_Request) {
				// удалим из url'а параметры своего input'а (openid_url и openid_action)
				// если он запрос
				foreach ($input->params as $k=>$v) {
					$login_url->unsetParam($v);
				}
			}
		}

		// по возможности,
		// добавим к параметрам запроса 'ref' -- адрес куда следует сделать редирект (нашему серваку),
		// после авторизации
		//
		// FIXME : 2007-11-20 : lucky
		// если в HTTP_REFERER слишком длинная строка, то для параметров ответа OpenId
		// может не хватить места
		// это случается, если делают логин со страницы на котороую OpenId уже вернул данные
		// (строка запроса растет лавинообразно)
		if (isset($_GET['ref'])) {
			$login_url->setParam('ref', $_GET['ref']);
		} else if ($_SERVER['HTTP_REFERER'] !== $login_url->getUrl()) {
			$login_url->setParam('ref', urldecode($_SERVER['HTTP_REFERER']));
		}
		debug(__METHOD__.'() approved URL: '.(string)$login_url);
		return $login_url->getUrl();
	}
	
	public function identify ($input)
	{
		$error = NULL;
		$this->cred = NULL;

		$trust_url = $this->trust_url;

		require_once 'openid/class.openid.php';
		$openid = new SimpleOpenID();
		
		$openid_action = is_object($input) ? $input->openid_action : NULL;
		$openid_mode   = isset($_GET['openid_mode']) ? $_GET['openid_mode'] : NULL;
		$identity_mode = isset($input->openid_login) ? "logpass" : "url";

		// EXAMPLE
		if ($openid_action == "login") { // Get identity from user and redirect browser to OpenID Server
		
			$openid->SetTrustRoot($trust_url);
			$openid->SetRequiredFields(array('email','fullname'));
			$openid->SetOptionalFields(array('dob','gender','postcode','country','language','timezone'));
			$openid->SetApprovedURL($this->getLoginUrl($input));  	// Send Response from OpenID server to this script	
			
			// это все про расширение протокола, возможностью аутентификации через логин и пароль
			switch ($identity_mode) {
				case "url":
				$openid->SetIdentity($this->getOpenid_url($input->openid_url));
				if ($openid->GetOpenIDServer()) {
					$openid->Redirect(); 	// This will redirect user to OpenID Server
					die();
		
				} else {
					$error = $openid->GetError();
					$this->cred = NULL;
					//echo "ERROR CODE: " . $error['code'] . "<br>";
					//echo "ERROR DESCRIPTION: " . $error['description'] . "<br>";
				}
				break;
				
				case "logpass":
				$openid->SetLoginPassword($input->openid_login, $input->openid_password);
				$openid->SetOpenIDServer($this->openid_server_url);
				$openid->Redirect(); 	// This will redirect user to OpenID Server
				die();
				break;
			}
						
			//exit;
		}
		else if ($openid_mode === 'id_res') { // возврат с сервера OpenID провайдера
			
			// Perform HTTP RL_Principal_Input_Request to OpenID server to validate key
			$openid->SetIdentity($_GET['openid_identity']);
			$openid_validation_result = $openid->ValidateWithServer();

			if ($openid_validation_result == true) { // OK HERE KEY IS VALID
				$user_login = $openid->OpenID_Standarize($_GET['openid_identity']);

				$data          = $this->loadResponse($_GET);
				$data['login'] = $user_login;
				$this->cred    = $user_login;
				// заодно сохраним профайл
				$this->profiles[$this->cred] = $data;
			} else if ($openid->IsError() == true) {			// ON THE WAY, WE GOT SOME ERROR
				$error = $openid->GetError();				
				
				error(__METHOD__.var_export($error, true));
				//echo "ERROR CODE: " . $error['code'] . "<br>";
				//echo "ERROR DESCRIPTION: " . $error['description'] . "<br>";
				$this->cred = NULL;
			} else {											// Signature Verification Failed
				//echo "INVALID AUTHORIZATION";
				error(__METHOD__.var_export($error, true));
				$this->error(2);
				$this->cred = NULL;
				
			}
		} else if ($openid_mode === 'cancel'){ // User Canceled your RL_Principal_Input_Request
			$this->cred = NULL;

			$this->error(1);
			//echo "USER CANCELED REQUEST";
		}

		if ($error) {
			switch($error['code']) {
				case 'OPENID_NOSERVERSFOUND':
				$this->error(3);
				break;
				case 'OPENID_SOCKETERROR':
				$this->error(4);
				break;
				case 'OPENID_SOCKETTIMEOUT':
				$this->error(5);
				break;
				case 'OPENID_CURL':
				$this->error(6, $error['description']);
			}
			

			error($this->error['description']);
		}
		return $this->cred;
	} // end of function identify
	
	public function getProfile ()
	{
		return $this->profiles[$this->cred];
	} // end of function getProfile

	//!a function
	private function loadResponse ($response, $php_style=true)
	/**
	 * Разобрать ответ Openid сервера.
	 *
	 * FIXME : 2007-11-20 : lucky
	 * эта функция должна быть в библиотеке для работы с OpenId
	 *
	 * FIXME : 2007-11-20 : lucky
	 * Проблема в том, что названия параметров Openid содержат точки '.',
	 * которые PHP, разбирая url (@see parse_str() function) меняет на '_'.
	 * Поэтому вместо 'openid.signed' имеем 'openid_signed'.
	 *
	 * @param array $response что-то типа $_GET, $_REQUEST, $_POST и т.п.
	 * @param bool $php_style если True, считаем что в именах параметров символ '.' заменен на '_'
	 *                        (когда первым параметром передается $_GET и т.п. -- так есть)
	 * @access public
	 * @return array('openid'=>array(), 'signed'=>array(), 'other'=>array())
	 *               'openid' содержит все параметры ответа openid сервера
	 *                        символ '.' в именах ключей как в $response (скорее всего заменен на '_')
	 *               'signed' содержит signed параметры ответа openid сервера
	 *                        символ '.' в именах ключей сохранен
	 *               'other'  содержит остальные параметры ответа, кторые не относятся к openid серверу
	 */
	{
		// все параметры ответа openid (включая signed)
		$openid   = array();
		// только signed параметры ответа openid
		$signed = array();
		// остальные параметры ответа
		$other = array();

		// массив 'подписанных' имен параметров
		$signed_keys = array();
		$signed_key_list = array_map('trim', explode(',', $response['openid_signed']));
		foreach ($signed_key_list as $name) {
			$php_key = $php_style ? strtr($name, '.', '_') : $name;
			$signed_keys[$php_key] = $name;
		}
		unset($signed_key_list);

		$openid_prefix = 'openid_';
		$openid_prefix_len = strlen($openid_prefix);
		foreach ($response as $k=>$v) {
			// если это параметр OpenId, то название начинается с $openid_prefix
			if (0 === strpos($k, $openid_prefix)) {
				$php_key = substr($k, $openid_prefix_len);
				if (isset($signed_keys[$php_key])) {
					$key = $signed_keys[$php_key];
					$signed[$key] = $v;
				}
				$openid[$php_key] = $v;
			} else {
				$other[$k] = $v;
			}
		}
		return array('openid'=>$openid, 'signed'=>$signed, 'other'=>$other);
	} // end of function loadResponse
}

